#![no_std]
#![doc = "GPIO driver for Rockchip RK3588 SoC"]

use core::ptr::{read_volatile, write_volatile};

pub mod regs;
pub use regs::{Direction, Level, TriggerType};

#[cfg(feature = "irq")]
pub mod irq;

/// Number of pins per GPIO bank
pub const PINS_PER_BANK: usize = 32;

/// RK3588 GPIO driver
pub struct RK3588Gpio {
    base_addr: usize,
}

impl RK3588Gpio {
    /// Create a new GPIO controller instance
    ///
    /// # Parameters
    /// - `base_addr`: Base address of the GPIO registers
    ///
    /// # Examples
    /// ```
    /// let gpio0 = RK3588Gpio::new(0xFD8A0000);
    /// ```
    pub const fn new(base_addr: usize) -> Self {
        Self { base_addr }
    }

    /// Read a register
    #[inline]
    fn read_reg(&self, offset: usize) -> u32 {
        unsafe { read_volatile((self.base_addr + offset) as *const u32) }
    }

    /// Write a register directly (without write-mask)
    /// Use this only when returning values for read-only registers; control registers should use write-mask writes.
    #[inline]
    fn write_reg(&self, offset: usize, value: u32) {
        unsafe { write_volatile((self.base_addr + offset) as *mut u32, value) }
    }

    /// Write to a register using the write-mask mechanism (RK3588 feature)
    ///
    /// # Parameters
    /// - `offset`: Register offset
    /// - `bit_index`: Bit index to modify (0-15, corresponds to the _L or _H registers)
    /// - `value`: Bit value (true = 1, false = 0)
    ///
    /// # Notes
    /// RK3588 GPIO register format:
    /// - Bits [31:16]: Write mask (1 = allow writing the corresponding data bit)
    /// - Bits [15:0]: Data bits
    ///
    /// To modify bit N, set bit (N + 16) to 1.
    #[inline]
    fn write_with_mask(&self, offset: usize, bit_index: u8, value: bool) {
        debug_assert!(bit_index < 16, "bit_index must be 0-15 for L/H registers");

        let mask = 1u32 << (bit_index + 16); // Write-mask bit
        let data = if value { 1u32 << bit_index } else { 0 };

        self.write_reg(offset, mask | data);
    }

    /// Set multiple bits at once (using the write-mask mechanism)
    ///
    /// # Parameters
    /// - `offset`: Register offset
    /// - `mask_bits`: Mask of bits to modify (0-15)
    /// - `value_bits`: Values for those bits
    #[inline]
    fn write_multi_with_mask(&self, offset: usize, mask_bits: u16, value_bits: u16) {
        let mask = (mask_bits as u32) << 16; // Write mask
        let data = value_bits as u32;

        self.write_reg(offset, mask | data);
    }

    /// Configure a pin direction
    ///
    /// # Parameters
    /// - `pin`: Pin index (0-31)
    /// - `direction`: Direction (input or output)
    ///
    /// # Examples
    /// ```
    /// gpio0.set_direction(5, Direction::Output); // Set pin 5 to output
    /// gpio0.set_direction(20, Direction::Input); // Set pin 20 to input
    /// ```
    pub fn set_direction(&self, pin: u8, direction: Direction) {
        if pin >= PINS_PER_BANK as u8 {
            log::error!("Invalid pin number: {}", pin);
            return;
        }

        let (offset, bit_index) = if pin < 16 {
            // Pins 0-15 use DDR_L
            (regs::offset::SWPORT_DDR_L, pin)
        } else {
            // Pins 16-31 use DDR_H
            (regs::offset::SWPORT_DDR_H, pin - 16)
        };

        let value = match direction {
            Direction::Output => true, // 1 = output
            Direction::Input => false, // 0 = input
        };

        self.write_with_mask(offset, bit_index, value);
    }

    /// Set the logic level for an output pin
    ///
    /// # Parameters
    /// - `pin`: Pin index (0-31)
    /// - `level`: Logic level (high or low)
    ///
    /// # Examples
    /// ```
    /// gpio0.set_value(5, Level::High); // Drive pin 5 high
    /// gpio0.set_value(20, Level::Low); // Drive pin 20 low
    /// ```
    pub fn set_value(&self, pin: u8, level: Level) {
        if pin >= PINS_PER_BANK as u8 {
            log::error!("Invalid pin number: {}", pin);
            return;
        }

        let (offset, bit_index) = if pin < 16 {
            // Pins 0-15 use DR_L
            (regs::offset::SWPORT_DR_L, pin)
        } else {
            // Pins 16-31 use DR_H
            (regs::offset::SWPORT_DR_H, pin - 16)
        };

        let value = match level {
            Level::High => true, // 1 = high level
            Level::Low => false, // 0 = low level
        };

        self.write_with_mask(offset, bit_index, value);
    }

    /// Read the value of a pin
    ///
    /// # Parameters
    /// - `pin`: Pin index (0-31)
    ///
    /// # Returns
    /// Pin level (high or low)
    ///
    /// # Notes
    /// GPIO_EXT_PORT is a 32-bit read-only register that mirrors the live state of all 32 pins.
    ///
    /// # Examples
    /// ```
    /// let level = gpio0.get_value(10); // Read the level of pin 10
    /// ```
    pub fn get_value(&self, pin: u8) -> Level {
        if pin >= PINS_PER_BANK as u8 {
            log::error!("Invalid pin number: {}", pin);
            return Level::Low;
        }

        // GPIO_EXT_PORT is a 32-bit register, one bit per pin
        let data = self.read_reg(regs::offset::EXT_PORT);
        let mask = 1u32 << pin;

        if (data & mask) != 0 {
            Level::High
        } else {
            Level::Low
        }
    }

    /// Toggle the logic level of an output pin
    pub fn toggle(&self, pin: u8) {
        let current = self.get_value(pin);
        match current {
            Level::High => self.set_value(pin, Level::Low),
            Level::Low => self.set_value(pin, Level::High),
        }
    }

    /// Configure a pin interrupt
    ///
    /// # Parameters
    /// - `pin`: Pin index (0-31)
    /// - `trigger`: Trigger type
    ///
    /// # Notes
    /// RK3588 supports five interrupt trigger types:
    /// - LevelLow: trigger on low level
    /// - LevelHigh: trigger on high level
    /// - EdgeFalling: trigger on falling edge
    /// - EdgeRising: trigger on rising edge
    /// - EdgeBoth: trigger on both edges (RK3588 specific)
    #[cfg(feature = "irq")]
    pub fn enable_interrupt(&self, pin: u8, trigger: TriggerType) {
        if pin >= PINS_PER_BANK as u8 {
            return;
        }

        let (offset_type, offset_polarity, offset_bothedge, offset_en, offset_mask, bit_index) =
            if pin < 16 {
                (
                    regs::offset::INT_TYPE_L,
                    regs::offset::INT_POLARITY_L,
                    regs::offset::INT_BOTHEDGE_L,
                    regs::offset::INT_EN_L,
                    regs::offset::INT_MASK_L,
                    pin,
                )
            } else {
                (
                    regs::offset::INT_TYPE_H,
                    regs::offset::INT_POLARITY_H,
                    regs::offset::INT_BOTHEDGE_H,
                    regs::offset::INT_EN_H,
                    regs::offset::INT_MASK_H,
                    pin - 16,
                )
            };

        // Configure interrupt type and polarity
        match trigger {
            TriggerType::LevelLow => {
                self.write_with_mask(offset_type, bit_index, false); // 0 = level-triggered
                self.write_with_mask(offset_polarity, bit_index, false); // 0 = low level/falling edge
                self.write_with_mask(offset_bothedge, bit_index, false); // 0 = disable both-edge
            }
            TriggerType::LevelHigh => {
                self.write_with_mask(offset_type, bit_index, false); // 0 = level-triggered
                self.write_with_mask(offset_polarity, bit_index, true); // 1 = high level/rising edge
                self.write_with_mask(offset_bothedge, bit_index, false); // 0 = disable both-edge
            }
            TriggerType::EdgeFalling => {
                self.write_with_mask(offset_type, bit_index, true); // 1 = edge-triggered
                self.write_with_mask(offset_polarity, bit_index, false); // 0 = falling edge
                self.write_with_mask(offset_bothedge, bit_index, false); // 0 = disable both-edge
            }
            TriggerType::EdgeRising => {
                self.write_with_mask(offset_type, bit_index, true); // 1 = edge-triggered
                self.write_with_mask(offset_polarity, bit_index, true); // 1 = rising edge
                self.write_with_mask(offset_bothedge, bit_index, false); // 0 = disable both-edge
            }
            TriggerType::EdgeBoth => {
                // In both-edge mode, INT_TYPE and INT_POLARITY are ignored when INT_BOTHEDGE = 1.
                self.write_with_mask(offset_bothedge, bit_index, true); // 1 = enable both-edge
            }
        }

        // Enable the interrupt
        self.write_with_mask(offset_en, bit_index, true);
        // Unmask it (0 = not masked)
        self.write_with_mask(offset_mask, bit_index, false);
    }

    /// Disable a pin interrupt
    #[cfg(feature = "irq")]
    pub fn disable_interrupt(&self, pin: u8) {
        if pin >= PINS_PER_BANK as u8 {
            return;
        }

        let (offset_en, bit_index) = if pin < 16 {
            (regs::offset::INT_EN_L, pin)
        } else {
            (regs::offset::INT_EN_H, pin - 16)
        };

        // Disable the interrupt
        self.write_with_mask(offset_en, bit_index, false);
    }

    /// Clear the interrupt state
    ///
    /// # Notes
    /// Only effective for edge-triggered interrupts; level-triggered interrupts must be cleared at the source.
    #[cfg(feature = "irq")]
    pub fn clear_interrupt(&self, pin: u8) {
        if pin >= PINS_PER_BANK as u8 {
            return;
        }

        let (offset, bit_index) = if pin < 16 {
            (regs::offset::PORT_EOI_L, pin)
        } else {
            (regs::offset::PORT_EOI_H, pin - 16)
        };

        // Write 1 to clear the interrupt; the bit auto-clears
        self.write_with_mask(offset, bit_index, true);
    }

    /// Read the masked interrupt status
    #[cfg(feature = "irq")]
    pub fn get_interrupt_status(&self) -> u32 {
        self.read_reg(regs::offset::INT_STATUS)
    }

    /// Read the raw interrupt status (before masking)
    #[cfg(feature = "irq")]
    pub fn get_interrupt_rawstatus(&self) -> u32 {
        self.read_reg(regs::offset::INT_RAWSTATUS)
    }

    /// Read the GPIO version ID
    ///
    /// # Returns
    /// Version ID (RK3588 should return 0x0101157C)
    pub fn get_version_id(&self) -> u32 {
        self.read_reg(regs::offset::VER_ID)
    }
}

// Implement Send and Sync so the driver can be shared across cores
unsafe impl Send for RK3588Gpio {}
unsafe impl Sync for RK3588Gpio {}
